#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com 
// 
// The source code in this file is covered under a dual-license scenario:
//   - Owner of a purchased license: SCLA 1.0
//   - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved

#region Using Directives
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using Technosoftware.DaAeHdaClient.Utilities;
#endregion

namespace Technosoftware.DaAeHdaClient.Com
{
    /// <summary>
    /// Exposes WIN32 and COM API functions.
    /// </summary>
    internal class ComUtils
    {
        #region NetApi Function Declarations
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct SERVER_INFO_100
        {
            public uint sv100_platform_id;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string sv100_name;
        }

        private const uint LEVEL_SERVER_INFO_100 = 100;
        private const uint LEVEL_SERVER_INFO_101 = 101;

        private const int MAX_PREFERRED_LENGTH = -1;

        private const uint SV_TYPE_WORKSTATION = 0x00000001;
        private const uint SV_TYPE_SERVER = 0x00000002;

        [DllImport("Netapi32.dll")]
        private static extern int NetServerEnum(
            IntPtr servername,
            uint level,
            out IntPtr bufptr,
            int prefmaxlen,
            out int entriesread,
            out int totalentries,
            uint servertype,
            IntPtr domain,
            IntPtr resume_handle);

        [DllImport("Netapi32.dll")]
        private static extern int NetApiBufferFree(IntPtr buffer);

        /// <summary>
        /// Enumerates computers on the local network.
        /// </summary>
        public static List<string> EnumComputers()
        {
            IntPtr pInfo;
            int totalEntries;

            int entriesRead;
            var result = NetServerEnum(
                IntPtr.Zero,
                LEVEL_SERVER_INFO_100,
                out pInfo,
                MAX_PREFERRED_LENGTH,
                out entriesRead,
                out totalEntries,
                SV_TYPE_WORKSTATION | SV_TYPE_SERVER,
                IntPtr.Zero,
                IntPtr.Zero);

            if (result != 0)
            {
                throw new ApplicationException("NetApi Error = " + string.Format("0x{0:X8}", result));
            }

            var computers = new List<string>();

            var pos = pInfo;

            for (var ii = 0; ii < entriesRead; ii++)
            {
                var info = (SERVER_INFO_100)Marshal.PtrToStructure(pos, typeof(SERVER_INFO_100));

                computers.Add(info.sv100_name);

                pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(SERVER_INFO_100)));
            }

            NetApiBufferFree(pInfo);

            return computers;
        }
        #endregion

        #region OLE32 Function/Interface Declarations
        private const int MAX_MESSAGE_LENGTH = 1024;

        private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
        private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

        [DllImport("Kernel32.dll")]
        private static extern int FormatMessageW(
            int dwFlags,
            IntPtr lpSource,
            int dwMessageId,
            int dwLanguageId,
            IntPtr lpBuffer,
            int nSize,
            IntPtr Arguments);

        [DllImport("Kernel32.dll")]
        private static extern int GetSystemDefaultLangID();

        [DllImport("Kernel32.dll")]
        private static extern int GetUserDefaultLangID();

        /// <summary>
        /// The WIN32 system default locale.
        /// </summary>
        public const int LOCALE_SYSTEM_DEFAULT = 0x800;

        /// <summary>
        /// The WIN32 user default locale.
        /// </summary>
        public const int LOCALE_USER_DEFAULT = 0x400;

        /// <summary>
        /// The base for the WIN32 FILETIME structure.
        /// </summary>
        private static readonly DateTime FILETIME_BaseTime = new DateTime(1601, 1, 1);

        /// <summary>
        /// WIN32 GUID struct declaration.
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct GUID
        {
            public int Data1;
            public short Data2;
            public short Data3;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public byte[] Data4;
        }

        /// <summary>
        /// The size, in bytes, of a VARIANT structure.
        /// </summary>
        private const int VARIANT_SIZE = 0x10;

        [DllImport("OleAut32.dll")]
        private static extern int VariantChangeTypeEx(
            IntPtr pvargDest,
            IntPtr pvarSrc,
            int lcid,
            ushort wFlags,
            short vt);

        /// <summary>
        /// Intializes a pointer to a VARIANT.
        /// </summary>
        [DllImport("oleaut32.dll")]
        private static extern void VariantInit(IntPtr pVariant);

        /// <summary>
        /// Frees all memory referenced by a VARIANT stored in unmanaged memory.
        /// </summary>
        [DllImport("oleaut32.dll")]
        public static extern void VariantClear(IntPtr pVariant);

        private const int DISP_E_TYPEMISMATCH = -0x7FFDFFFB; // 0x80020005
        private const int DISP_E_OVERFLOW = -0x7FFDFFF6; // 0x8002000A

        private const int VARIANT_NOVALUEPROP = 0x01;
        private const int VARIANT_ALPHABOOL = 0x02; // For VT_BOOL to VT_BSTR conversions convert to "True"/"False" instead of

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct SOLE_AUTHENTICATION_SERVICE
        {
            public uint dwAuthnSvc;
            public uint dwAuthzSvc;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pPrincipalName;
            public int hr;
        }

        private const uint RPC_C_AUTHN_NONE = 0;
        private const uint RPC_C_AUTHN_DCE_PRIVATE = 1;
        private const uint RPC_C_AUTHN_DCE_PUBLIC = 2;
        private const uint RPC_C_AUTHN_DEC_PUBLIC = 4;
        private const uint RPC_C_AUTHN_GSS_NEGOTIATE = 9;
        private const uint RPC_C_AUTHN_WINNT = 10;
        private const uint RPC_C_AUTHN_GSS_SCHANNEL = 14;
        private const uint RPC_C_AUTHN_GSS_KERBEROS = 16;
        private const uint RPC_C_AUTHN_DPA = 17;
        private const uint RPC_C_AUTHN_MSN = 18;
        private const uint RPC_C_AUTHN_DIGEST = 21;
        private const uint RPC_C_AUTHN_MQ = 100;
        private const uint RPC_C_AUTHN_DEFAULT = 0xFFFFFFFF;

        private const uint RPC_C_AUTHZ_NONE = 0;
        private const uint RPC_C_AUTHZ_NAME = 1;
        private const uint RPC_C_AUTHZ_DCE = 2;
        private const uint RPC_C_AUTHZ_DEFAULT = 0xffffffff;

        private const uint RPC_C_AUTHN_LEVEL_DEFAULT = 0;
        private const uint RPC_C_AUTHN_LEVEL_NONE = 1;
        private const uint RPC_C_AUTHN_LEVEL_CONNECT = 2;
        private const uint RPC_C_AUTHN_LEVEL_CALL = 3;
        private const uint RPC_C_AUTHN_LEVEL_PKT = 4;
        private const uint RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5;
        private const uint RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6;

        private const uint RPC_C_IMP_LEVEL_ANONYMOUS = 1;
        private const uint RPC_C_IMP_LEVEL_IDENTIFY = 2;
        private const uint RPC_C_IMP_LEVEL_IMPERSONATE = 3;
        private const uint RPC_C_IMP_LEVEL_DELEGATE = 4;

        private const uint EOAC_NONE = 0x00;
        private const uint EOAC_MUTUAL_AUTH = 0x01;
        private const uint EOAC_CLOAKING = 0x10;
        private const uint EOAC_STATIC_CLOAKING = 0x20;
        private const uint EOAC_DYNAMIC_CLOAKING = 0x40;
        private const uint EOAC_SECURE_REFS = 0x02;
        private const uint EOAC_ACCESS_CONTROL = 0x04;
        private const uint EOAC_APPID = 0x08;


        /// <returns>If function succeeds, it returns 0(S_OK). Otherwise, it returns an error code.</returns>
        [DllImport("ole32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern int CoInitializeEx(
            [In, Optional] IntPtr pvReserved,
            [In] COINIT dwCoInit //DWORD
            );

        [DllImport("ole32.dll")]
        private static extern int CoInitializeSecurity(
            IntPtr pSecDesc,
            int cAuthSvc,
            SOLE_AUTHENTICATION_SERVICE[] asAuthSvc,
            IntPtr pReserved1,
            uint dwAuthnLevel,
            uint dwImpLevel,
            IntPtr pAuthList,
            uint dwCapabilities,
            IntPtr pReserved3);

        [DllImport("ole32.dll")]
        private static extern int CoQueryProxyBlanket(
            [MarshalAs(UnmanagedType.IUnknown)]
            object pProxy,
            ref uint pAuthnSvc,
            ref uint pAuthzSvc,
            [MarshalAs(UnmanagedType.LPWStr)]
            ref string pServerPrincName,
            ref uint pAuthnLevel,
            ref uint pImpLevel,
            ref IntPtr pAuthInfo,
            ref uint pCapabilities);

        [DllImport("ole32.dll")]
        private static extern int CoSetProxyBlanket(
            [MarshalAs(UnmanagedType.IUnknown)]
            object pProxy,
            uint pAuthnSvc,
            uint pAuthzSvc,
            IntPtr pServerPrincName,
            uint pAuthnLevel,
            uint pImpLevel,
            IntPtr pAuthInfo,
            uint pCapabilities);

        private static readonly IntPtr COLE_DEFAULT_PRINCIPAL = new IntPtr(-1);
        private static readonly IntPtr COLE_DEFAULT_AUTHINFO = new IntPtr(-1);


        private enum COINIT : uint //tagCOINIT
        {
            COINIT_MULTITHREADED = 0x0, //Initializes the thread for multi-threaded object concurrency.
            COINIT_APARTMENTTHREADED = 0x2, //Initializes the thread for apartment-threaded object concurrency
            COINIT_DISABLE_OLE1DDE = 0x4, //Disables DDE for OLE1 support
            COINIT_SPEED_OVER_MEMORY = 0x8, //Trade memory for speed
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct COSERVERINFO
        {
            public uint dwReserved1;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pwszName;
            public IntPtr pAuthInfo;
            public uint dwReserved2;
        };

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct COAUTHINFO
        {
            public uint dwAuthnSvc;
            public uint dwAuthzSvc;
            public IntPtr pwszServerPrincName;
            public uint dwAuthnLevel;
            public uint dwImpersonationLevel;
            public IntPtr pAuthIdentityData;
            public uint dwCapabilities;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct COAUTHIDENTITY
        {
            public IntPtr User;
            public uint UserLength;
            public IntPtr Domain;
            public uint DomainLength;
            public IntPtr Password;
            public uint PasswordLength;
            public uint Flags;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct MULTI_QI
        {
            public IntPtr iid;
            [MarshalAs(UnmanagedType.IUnknown)]
            public object pItf;
            public uint hr;
        }

        private const uint CLSCTX_INPROC_SERVER = 0x1;
        private const uint CLSCTX_INPROC_HANDLER = 0x2;
        private const uint CLSCTX_LOCAL_SERVER = 0x4;
        private const uint CLSCTX_REMOTE_SERVER = 0x10;
        private const uint CLSCTX_DISABLE_AAA = 0x8000;

        private static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");

        private const uint SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1;
        private const uint SEC_WINNT_AUTH_IDENTITY_UNICODE = 0x2;

        [DllImport("ole32.dll")]
        private static extern void CoCreateInstanceEx(
            ref Guid clsid,
            [MarshalAs(UnmanagedType.IUnknown)]
            object           punkOuter,
            uint dwClsCtx,
            [In]
            ref COSERVERINFO pServerInfo,
            uint dwCount,
            [In, Out]
            MULTI_QI[]       pResults);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct LICINFO
        {
            public int cbLicInfo;
            [MarshalAs(UnmanagedType.Bool)]
            public bool fRuntimeKeyAvail;
            [MarshalAs(UnmanagedType.Bool)]
            public bool fLicVerified;
        }

        [ComImport]
        [GuidAttribute("00000001-0000-0000-C000-000000000046")]
        [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IClassFactory
        {
            void CreateInstance(
                [MarshalAs(UnmanagedType.IUnknown)]
                object punkOuter,
                [MarshalAs(UnmanagedType.LPStruct)]
                Guid riid,
                [MarshalAs(UnmanagedType.Interface)]
                [Out] out object ppvObject);

            void LockServer(
                [MarshalAs(UnmanagedType.Bool)]
                bool fLock);
        }

        [ComImport]
        [GuidAttribute("B196B28F-BAB4-101A-B69C-00AA00341D07")]
        [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IClassFactory2
        {
            void CreateInstance(
                [MarshalAs(UnmanagedType.IUnknown)]
                object punkOuter,
                [MarshalAs(UnmanagedType.LPStruct)]
                Guid riid,
                [MarshalAs(UnmanagedType.Interface)]
                [Out] out object ppvObject);

            void LockServer(
                [MarshalAs(UnmanagedType.Bool)]
                bool fLock);

            void GetLicInfo(
                [In, Out] ref LICINFO pLicInfo);

            void RequestLicKey(
                int dwReserved,
                [MarshalAs(UnmanagedType.BStr)]
                string pbstrKey);

            void CreateInstanceLic(
                [MarshalAs(UnmanagedType.IUnknown)]
                object punkOuter,
                [MarshalAs(UnmanagedType.IUnknown)]
                object punkReserved,
                [MarshalAs(UnmanagedType.LPStruct)]
                Guid riid,
                [MarshalAs(UnmanagedType.BStr)]
                string bstrKey,
                [MarshalAs(UnmanagedType.IUnknown)]
                [Out] out object ppvObject);
        }

        [DllImport("ole32.dll")]
        private static extern void CoGetClassObject(
            [MarshalAs(UnmanagedType.LPStruct)]
            Guid clsid,
            uint dwClsContext,
            [In] ref COSERVERINFO pServerInfo,
            [MarshalAs(UnmanagedType.LPStruct)]
            Guid riid,
            [MarshalAs(UnmanagedType.IUnknown)]
            [Out] out object ppv);

        private const int LOGON32_PROVIDER_DEFAULT = 0;
        private const int LOGON32_LOGON_INTERACTIVE = 2;
        private const int LOGON32_LOGON_NETWORK = 3;

        private const int SECURITY_ANONYMOUS = 0;
        private const int SECURITY_IDENTIFICATION = 1;
        private const int SECURITY_IMPERSONATION = 2;
        private const int SECURITY_DELEGATION = 3;

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool LogonUser(
            string lpszUsername,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private extern static bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private extern static bool DuplicateToken(
            IntPtr ExistingTokenHandle,
            int SECURITY_IMPERSONATION_LEVEL,
            ref IntPtr DuplicateTokenHandle);
        #endregion

        #region ServerInfo Class
        /// <summary>
        /// A class used to allocate and deallocate the elements of a COSERVERINFO structure.
        /// </summary>
        class ServerInfo
        {
            #region internal interface
            /// <summary>
            /// Allocates a COSERVERINFO structure.
            /// </summary>
            public COSERVERINFO Allocate(string hostName, OpcUserIdentity identity)
            {
                // initialize server info structure.
                var serverInfo = new COSERVERINFO();

                serverInfo.pwszName = hostName;
                serverInfo.pAuthInfo = IntPtr.Zero;
                serverInfo.dwReserved1 = 0;
                serverInfo.dwReserved2 = 0;

                // no authentication for default identity
                if (OpcUserIdentity.IsDefault(identity))
                {
                    return serverInfo;
                }

                m_hUserName = GCHandle.Alloc(identity.Username, GCHandleType.Pinned);
                m_hPassword = GCHandle.Alloc(identity.Password, GCHandleType.Pinned);
                m_hDomain = GCHandle.Alloc(identity.Domain, GCHandleType.Pinned);

                m_hIdentity = new GCHandle();

                // create identity structure.
                var authIdentity = new COAUTHIDENTITY();

                authIdentity.User = m_hUserName.AddrOfPinnedObject();
                authIdentity.UserLength = (uint)((identity.Username != null) ? identity.Username.Length : 0);
                authIdentity.Password = m_hPassword.AddrOfPinnedObject();
                authIdentity.PasswordLength = (uint)((identity.Password != null) ? identity.Password.Length : 0);
                authIdentity.Domain = m_hDomain.AddrOfPinnedObject();
                authIdentity.DomainLength = (uint)((identity.Domain != null) ? identity.Domain.Length : 0);
                authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;

                m_hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned);

                // create authorization info structure.
                var authInfo = new COAUTHINFO();

                authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT;
                authInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE;
                authInfo.pwszServerPrincName = IntPtr.Zero;
                authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT;
                authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
                authInfo.pAuthIdentityData = m_hIdentity.AddrOfPinnedObject();
                authInfo.dwCapabilities = EOAC_NONE; // EOAC_DYNAMIC_CLOAKING;

                m_hAuthInfo = GCHandle.Alloc(authInfo, GCHandleType.Pinned);

                // update server info structure.
                serverInfo.pAuthInfo = m_hAuthInfo.AddrOfPinnedObject();

                return serverInfo;
            }

            /// <summary>
            /// Deallocated memory allocated when the COSERVERINFO structure was created.
            /// </summary>
            public void Deallocate()
            {
                if (m_hUserName.IsAllocated) m_hUserName.Free();
                if (m_hPassword.IsAllocated) m_hPassword.Free();
                if (m_hDomain.IsAllocated) m_hDomain.Free();
                if (m_hIdentity.IsAllocated) m_hIdentity.Free();
                if (m_hAuthInfo.IsAllocated) m_hAuthInfo.Free();
            }
            #endregion

            #region Private Members
            private GCHandle m_hUserName;
            private GCHandle m_hPassword;
            private GCHandle m_hDomain;
            private GCHandle m_hIdentity;
            private GCHandle m_hAuthInfo;
            #endregion
        }
        #endregion

        #region Initialization Functions
        /// <summary>
        /// Initializes COM security.
        /// </summary>
        public static void InitializeSecurity()
        {
            var error = CoInitializeSecurity(
                IntPtr.Zero,
                -1,
                null,
                IntPtr.Zero,
                RPC_C_AUTHN_LEVEL_CONNECT,
                RPC_C_IMP_LEVEL_IMPERSONATE,
                IntPtr.Zero,
                EOAC_DYNAMIC_CLOAKING,
                IntPtr.Zero);

            // this call will fail in the debugger if the 
            // 'Debug | Enable Visual Studio Hosting Process'  
            // option is checked in the project properties. 
            if (error != 0)
            {
                // throw new ExternalException("CoInitializeSecurity: " + GetSystemMessage(error), error);
            }
        }

        /// <summary>
        /// Determines if the host is the local host.
        /// </summary>
        private static bool IsLocalHost(string hostName)
        {
            // lookup requested host.
            var requestedHost = Dns.GetHostEntry(hostName);

            if (requestedHost == null || requestedHost.AddressList == null)
            {
                return true;
            }

            // check for loopback.
            for (var ii = 0; ii < requestedHost.AddressList.Length; ii++)
            {
                var requestedIP = requestedHost.AddressList[ii];

                if (requestedIP == null || requestedIP.Equals(IPAddress.Loopback))
                {
                    return true;
                }
            }

            // lookup local host.
            var localHost = Dns.GetHostEntry(Dns.GetHostName());

            if (localHost == null || localHost.AddressList == null)
            {
                return false;
            }

            // check for localhost.
            for (var ii = 0; ii < requestedHost.AddressList.Length; ii++)
            {
                var requestedIP = requestedHost.AddressList[ii];

                for (var jj = 0; jj < localHost.AddressList.Length; jj++)
                {
                    if (requestedIP.Equals(localHost.AddressList[jj]))
                    {
                        return true;
                    }
                }
            }

            // must be remote.
            return false;
        }

        /// <summary>
        /// Creates an instance of a COM server using the specified license key.
        /// </summary>
        public static object CreateInstance(Guid clsid, string hostName, OpcUserIdentity identity)
        {
            return CreateInstance1(clsid, hostName, identity);
        }

        /// <summary>
        /// Creates an instance of a COM server.
        /// </summary>
        public static object CreateInstance1(Guid clsid, string hostName, OpcUserIdentity identity)
        {
            var serverInfo = new ServerInfo();
            var coserverInfo = serverInfo.Allocate(hostName, identity);

            var hIID = GCHandle.Alloc(IID_IUnknown, GCHandleType.Pinned);

            var results = new MULTI_QI[1];

            results[0].iid = hIID.AddrOfPinnedObject();
            results[0].pItf = null;
            results[0].hr = 0;

            try
            {
                // check whether connecting locally or remotely.
                var clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER;

                if (!string.IsNullOrEmpty(hostName) && hostName != "localhost")
                {
                    clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER;
                }

                // create an instance.
                CoCreateInstanceEx(
                    ref clsid,
                    null,
                    clsctx,
                    ref coserverInfo,
                    1,
                    results);
            }
            finally
            {
                if (hIID.IsAllocated) hIID.Free();
                serverInfo.Deallocate();
            }

            if (results[0].hr != 0)
            {
                throw new OpcResultException(new OpcResult(OpcResult.CONNECT_E_NOCONNECTION.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Could not create COM server '{0}' on host '{1}'. Reason: {2}.", clsid, hostName, GetSystemMessage((int)results[0].hr, LOCALE_SYSTEM_DEFAULT)));
            }

            return results[0].pItf;
        }

        // COM impersonation is a nice feature but variations between behavoirs on different
        // windows platforms make it virtually impossible to support. This code is left here 
        // in case it becomes a critical requirement in the future.
#if COM_IMPERSONATION_SUPPORT
        /// <summary>
        /// Returns the WindowsIdentity associated with a UserIdentity.
        /// </summary>
        public static WindowsPrincipal GetPrincipalFromUserIdentity(UserIdentity user)
        {
            if (UserIdentity.IsDefault(user))
            {
                return null;
            }

            // validate the credentials.
		    IntPtr token = IntPtr.Zero;

		    bool result = LogonUser(
                user.Username,
                user.Domain,
                user.Password, 
			    LOGON32_LOGON_NETWORK, 
			    LOGON32_PROVIDER_DEFAULT,
			    ref token);

		    if (!result)
		    {               
				throw new OpcResultException(new OpcResult((int)OpcResult.CONNECT_E_NOCONNECTION.Code, OpcResult.FuncCallType.SysFuncCall, null), String.Format("Could not logon as user '{0}'. Reason: {1}.", user.Username, GetSystemMessage(Marshal.GetLastWin32Error(), LOCALE_SYSTEM_DEFAULT)));

                throw OpcResultException.Create(
                    StatusCodes.BadIdentityTokenRejected, 
                    "Could not logon as user '{0}'. Reason: {1}.", 
                    user.Username, 
                    GetSystemMessage(Marshal.GetLastWin32Error(), LOCALE_SYSTEM_DEFAULT));
		    }
            
            try
            {
                // create the windows identity.
                WindowsIdentity identity = new WindowsIdentity(token);

                // validate the identity.
                identity.Impersonate();

                // return a principal.
                return new WindowsPrincipal(identity);
            }
            finally
            {                    
			    CloseHandle(token);
            }
        }

        /// <summary>
        /// Sets the security settings for the proxy.
        /// </summary>
        public static void SetProxySecurity(object server, UserIdentity user)
        {
            // allocate the 
            GCHandle hUserName = GCHandle.Alloc(user.Username, GCHandleType.Pinned);
            GCHandle hPassword = GCHandle.Alloc(user.Password, GCHandleType.Pinned);
            GCHandle hDomain   = GCHandle.Alloc(user.Domain,   GCHandleType.Pinned);

            GCHandle hIdentity = new GCHandle();

            // create identity structure.
            COAUTHIDENTITY authIdentity = new COAUTHIDENTITY();

            authIdentity.User           = hUserName.AddrOfPinnedObject();
            authIdentity.UserLength     = (uint)((user.Username != null) ? user.Username.Length : 0);
            authIdentity.Password       = hPassword.AddrOfPinnedObject();
            authIdentity.PasswordLength = (uint)((user.Password != null) ? user.Password.Length : 0);
            authIdentity.Domain         = hDomain.AddrOfPinnedObject();
            authIdentity.DomainLength   = (uint)((user.Domain != null) ? user.Domain.Length : 0);
            authIdentity.Flags          = SEC_WINNT_AUTH_IDENTITY_UNICODE;

            hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned);
            
            try
            {
                SetProxySecurity(server, hIdentity.AddrOfPinnedObject());
            }
            finally
            {
                hUserName.Free();
                hPassword.Free();
                hDomain.Free();
                hIdentity.Free();
            }
        }

        /// <summary>
        /// Sets the security settings for the proxy.
        /// </summary>
        public static void SetProxySecurity(object server, IntPtr pAuthInfo)
        {
            // get the existing proxy settings.
            uint pAuthnSvc = 0;
            uint pAuthzSvc = 0;
            string pServerPrincName = "";
            uint pAuthnLevel = 0;
            uint pImpLevel = 0;
            IntPtr pAuthInfo2 = IntPtr.Zero;
            uint pCapabilities = 0;

            CoQueryProxyBlanket(
                server,
                ref pAuthnSvc,
                ref pAuthzSvc,
                ref pServerPrincName,
                ref pAuthnLevel,
                ref pImpLevel,
                ref pAuthInfo2,
                ref pCapabilities);

            pAuthnSvc = RPC_C_AUTHN_WINNT;
            pAuthzSvc = RPC_C_AUTHZ_NONE;
            pAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT;
            pImpLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
            pCapabilities = EOAC_DYNAMIC_CLOAKING;
            
            // update proxy security settings.
            CoSetProxyBlanket(
                server,
                pAuthnSvc,
                pAuthzSvc,
                COLE_DEFAULT_PRINCIPAL,
                pAuthnLevel,
                pImpLevel,
                pAuthInfo,
                pCapabilities);
        }
		
		/// <summary>
		/// Creates an instance of a COM server using the specified license key.
		/// </summary>
		public static object CreateInstance2(Guid clsid, string hostName, UserIdentity identity)
		{
            // validate the host name before proceeding (exception thrown if host is not valid).
            bool isLocalHost = IsLocalHost(hostName);

            // allocate the connection info.
			ServerInfo    serverInfo   = new ServerInfo();
			COSERVERINFO  coserverInfo = serverInfo.Allocate(hostName, identity);
			object        instance     = null; 
			IClassFactory factory      = null; 
			
			try
			{
                // create the factory.
                object server = null;

				CoGetClassObject(
					clsid,
					(isLocalHost)?CLSCTX_LOCAL_SERVER:CLSCTX_REMOTE_SERVER,
					ref coserverInfo,
					IID_IUnknown,
					out server);

                // SetProxySecurity(server, coserverInfo.pAuthInfo);
                
                factory = (IClassFactory)server;

                // check for valid factory.
                if (factory == null)
                {
                    throw OpcResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory for COM server '{0}' on host '{1}'.", clsid, hostName);
                }

                // SetProxySecurity(factory, coserverInfo.pAuthInfo);

			    factory.CreateInstance(null, IID_IUnknown, out instance);

                // SetProxySecurity(instance, coserverInfo.pAuthInfo);
			}
			finally
			{
				serverInfo.Deallocate();
			}

            return instance;
		}	

		/// <summary>
		/// Creates an instance of a COM server using the specified license key.
		/// </summary>
		public static object CreateInstanceWithLicenseKey(Guid clsid, string hostName, UserIdentity identity, string licenseKey)
		{
			ServerInfo     serverInfo   = new ServerInfo();
			COSERVERINFO   coserverInfo = serverInfo.Allocate(hostName, identity);
			object         instance     = null; 
			IClassFactory2 factory      = null; 

			try
			{
				// check whether connecting locally or remotely.
				uint clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER;

				if (hostName != null && hostName.Length > 0)
				{
					clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER;
				}

				// get the class factory.
				object server = null;

				CoGetClassObject(
					clsid,
					clsctx,
					ref coserverInfo,
					typeof(IClassFactory2).GUID,
					out server);

                // SetProxySecurity(server, coserverInfo.pAuthInfo);
                
                factory = (IClassFactory2)server;

                // check for valid factory.
                if (factory == null)
                {
                    throw OpcResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory2 for COM server '{0}' on host '{1}'.", clsid, hostName);
                }

                // SetProxySecurity(factory, coserverInfo.pAuthInfo);

				// create instance.
				factory.CreateInstanceLic(
					null,
					null,
					IID_IUnknown,
					licenseKey,
                    out instance);

                // SetProxySecurity(instance, coserverInfo.pAuthInfo);
			}
			finally
			{
				serverInfo.Deallocate();
                ComUtils.ReleaseServer(factory);
			}
			  
			return instance;
		}	
#endif
        #endregion


        #region Conversion Functions
#if TEST

		/// <summary>
		/// Tests if the specified string matches the specified pattern.
		/// </summary>
		public static bool Match(string target, string pattern, bool caseSensitive)
		{
			// an empty pattern always matches.
			if (pattern == null || pattern.Length == 0)
			{
				return true;
			}

			// an empty string never matches.
			if (target == null || target.Length == 0)
			{
				return false;
			}

			// check for exact match
			if (caseSensitive)
			{
				if (target == pattern)
				{
					return true;
				}
			}
			else
			{
				if (target.ToLower() == pattern.ToLower())
				{
					return true;
				}
			}
 
			char c;
			char p;
			char l;

			int pIndex = 0;
			int tIndex = 0;

			while (tIndex < target.Length && pIndex < pattern.Length)
			{
				p = ConvertCase(pattern[pIndex++], caseSensitive);

				if (pIndex > pattern.Length)
				{
					return (tIndex >= target.Length); // if end of string true
				}
	
				switch (p)
				{
					// match zero or more char.
					case '*':
					{
                        while (pIndex < pattern.Length && pattern[pIndex] == '*')
                        {
                            pIndex++;
                        }

						while (tIndex < target.Length) 
						{   
							if (Match(target.Substring(tIndex++), pattern.Substring(pIndex), caseSensitive))
							{
								return true;
							}
						}
			
						return Match(target, pattern.Substring(pIndex), caseSensitive);
					}

					// match any one char.
					case '?':
					{
						// check if end of string when looking for a single character.
						if (tIndex >= target.Length) 
						{
							return false;  
						}

						// check if end of pattern and still string data left.
						if (pIndex >= pattern.Length && tIndex < target.Length-1)
						{
							return false;
						}

						tIndex++;
						break;
					}

					// match char set 
					case '[': 
					{
						c = ConvertCase(target[tIndex++], caseSensitive);

						if (tIndex > target.Length)
						{
							return false; // syntax 
						}

						l = '\0'; 

						// match a char if NOT in set []
						if (pattern[pIndex] == '!') 
						{
							++pIndex;

							p = ConvertCase(pattern[pIndex++], caseSensitive);

							while (pIndex < pattern.Length) 
							{
								if (p == ']') // if end of char set, then 
								{
									break; // no match found 
								}

								if (p == '-') 
								{
									// check a range of chars? 
									p = ConvertCase(pattern[pIndex], caseSensitive);

									// get high limit of range 
									if (pIndex > pattern.Length || p == ']')
									{
										return false; // syntax 
									}

									if (c >= l && c <= p) 
									{
										return false; // if in range, return false
									}
								} 

								l = p;
						
								if (c == p) // if char matches this element 
								{
									return false; // return false 
								}
								
								p = ConvertCase(pattern[pIndex++], caseSensitive);
							} 
						}

						// match if char is in set []
						else 
						{
							p = ConvertCase(pattern[pIndex++], caseSensitive);

							while (pIndex < pattern.Length) 
							{
								if (p == ']') // if end of char set, then no match found 
								{
									return false;
								}

								if (p == '-') 
								{   
									// check a range of chars? 
									p = ConvertCase(pattern[pIndex], caseSensitive);
							
									// get high limit of range 
									if (pIndex > pattern.Length || p == ']')
									{
										return false; // syntax 
									}

									if (c >= l  &&  c <= p) 
									{
										break; // if in range, move on 
									}
								} 

								l = p;
						
								if (c == p) // if char matches this element move on 
								{
									break;           
								}
								
								p = ConvertCase(pattern[pIndex++], caseSensitive);
							} 

							while (pIndex < pattern.Length && p != ']') // got a match in char set skip to end of set
							{
								p = pattern[pIndex++];             
							}
						}

						break; 
					}

					// match digit.
					case '#':
					{
						c = target[tIndex++]; 

						if (!Char.IsDigit(c))
						{
							return false; // not a digit
						}

						break;
					}

					// match exact char.
					default: 
					{
						c = ConvertCase(target[tIndex++], caseSensitive); 
				
						if (c != p) // check for exact char
						{
							return false; // not a match
						}

						// check if end of pattern and still string data left.
						if (pIndex >= pattern.Length && tIndex < target.Length-1)
						{
							return false;
						}

						break;
					}
				} 
			} 

			return true;
		} 

		// ConvertCase
		private static char ConvertCase(char c, bool caseSensitive)
		{
			return (caseSensitive)?c:Char.ToUpper(c);
		}

		/// <summary>
		/// Unmarshals and frees an array of HRESULTs.
		/// </summary>
		public static int[] GetStatusCodes(ref IntPtr pArray, int size, bool deallocate)
		{
			if (pArray == IntPtr.Zero || size <= 0)
			{
				return null;
			}

			// unmarshal HRESULT array.
			int[] output = new int[size];
			Marshal.Copy(pArray, output, 0, size);

			if (deallocate)
			{
				Marshal.FreeCoTaskMem(pArray);
				pArray = IntPtr.Zero;
			}

			return output;
		}

		/// <summary>
		/// Unmarshals and frees an array of 32 bit integers.
		/// </summary>
		public static int[] GetInt32s(ref IntPtr pArray, int size, bool deallocate)
		{
			if (pArray == IntPtr.Zero || size <= 0)
			{
				return null;
			}

			int[] array = new int[size];
			Marshal.Copy(pArray, array, 0, size);

			if (deallocate)
			{
				Marshal.FreeCoTaskMem(pArray);
				pArray = IntPtr.Zero;
			}

			return array;
        }

        /// <summary>
        /// Unmarshals and frees an array of 32 bit integers.
        /// </summary>
        public static int[] GetUInt32s(ref IntPtr pArray, int size, bool deallocate)
        {
            if (pArray == IntPtr.Zero || size <= 0)
            {
                return null;
            }

            int[] array = new int[size];
            Marshal.Copy(pArray, array, 0, size);

            if (deallocate)
            {
                Marshal.FreeCoTaskMem(pArray);
                pArray = IntPtr.Zero;
            }

            return array;
        }


		/// <summary>
		/// Allocates and marshals an array of 32 bit integers.
		/// </summary>
		public static IntPtr GetInt32s(int[] input)
		{
			IntPtr output = IntPtr.Zero;

			if (input != null)
			{
				output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Int32))*input.Length);
				Marshal.Copy(input, 0, output, input.Length);
			}

			return output;
		}

		/// <summary>
		/// Unmarshals and frees a array of 16 bit integers.
		/// </summary>
		public static short[] GetInt16s(ref IntPtr pArray, int size, bool deallocate)
		{
			if (pArray == IntPtr.Zero || size <= 0)
			{
				return null;
			}

			short[] array = new short[size];
			Marshal.Copy(pArray, array, 0, size);

			if (deallocate)
			{
				Marshal.FreeCoTaskMem(pArray);
				pArray = IntPtr.Zero;
			}

			return array;
		}

		/// <summary>
		/// Allocates and marshals an array of 16 bit integers.
		/// </summary>
		public static IntPtr GetInt16s(short[] input)
		{
			IntPtr output = IntPtr.Zero;

			if (input != null)
			{
				output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Int16))*input.Length);
				Marshal.Copy(input, 0, output, input.Length);
			}

			return output;
		}

		/// <summary>
		/// Marshals an array of strings into a unmanaged memory buffer
		/// </summary>
		/// <param name="values">The array of strings to marshal</param>
		/// <returns>The pointer to the unmanaged memory buffer</returns>
		public static IntPtr GetUnicodeStrings(string[] values)
		{
			int size = (values != null)?values.Length:0;

			if (size <= 0)
			{
				return IntPtr.Zero;
			}

			IntPtr pValues = IntPtr.Zero;

			int[] pointers = new int[size];
			
			for (int ii = 0; ii < size; ii++)
			{
				pointers[ii] = (int)Marshal.StringToCoTaskMemUni(values[ii]);
			}

			pValues = Marshal.AllocCoTaskMem(values.Length*Marshal.SizeOf(typeof(IntPtr)));
			Marshal.Copy(pointers, 0, pValues, size);

			return pValues;
		}

		/// <summary>
		/// Unmarshals and frees a array of unicode strings.
		/// </summary>
		public static string[] GetUnicodeStrings(ref IntPtr pArray, int size, bool deallocate)
		{
			if (pArray == IntPtr.Zero || size <= 0)
			{
				return null;
			}

            IntPtr[] pointers = new IntPtr[size];
			Marshal.Copy(pArray, pointers, 0, size);

			string[] strings = new string[size];

			for (int ii = 0; ii < size; ii++)
			{
				IntPtr pString = pointers[ii];
				strings[ii] = Marshal.PtrToStringUni(pString);
				if (deallocate) Marshal.FreeCoTaskMem(pString);
			}

			if (deallocate)
			{
				Marshal.FreeCoTaskMem(pArray);
				pArray = IntPtr.Zero;
			}

			return strings;
		}

		/// <summary>
		/// Marshals a DateTime as a WIN32 FILETIME.
		/// </summary>
		/// <param name="datetime">The DateTime object to marshal</param>
		/// <returns>The WIN32 FILETIME</returns>
		public static System.Runtime.InteropServices.ComTypes.FILETIME GetFILETIME(DateTime datetime)
		{
			System.Runtime.InteropServices.ComTypes.FILETIME filetime;

			if (datetime <= FILETIME_BaseTime)
			{
				filetime.dwHighDateTime = 0;
				filetime.dwLowDateTime  = 0;
				return filetime;
			}

			// adjust for WIN32 FILETIME base.
			long ticks = 0;
			ticks = datetime.Subtract(new TimeSpan(FILETIME_BaseTime.Ticks)).Ticks;
		
			filetime.dwHighDateTime = (int)((ticks>>32) & 0xFFFFFFFF);
			filetime.dwLowDateTime  = (int)(ticks & 0xFFFFFFFF);

			return filetime;
		}

		/// <summary>
		/// Unmarshals a WIN32 FILETIME from a pointer.
		/// </summary>
		/// <param name="pFiletime">A pointer to a FILETIME structure.</param>
		/// <returns>A DateTime object.</returns>
		public static DateTime GetDateTime(IntPtr pFiletime)
		{
			if (pFiletime == IntPtr.Zero)
			{
				return DateTime.MinValue;
			}

			return GetDateTime((System.Runtime.InteropServices.ComTypes.FILETIME)Marshal.PtrToStructure(pFiletime, typeof(System.Runtime.InteropServices.ComTypes.FILETIME)));
		}

		/// <summary>
		/// Unmarshals a WIN32 FILETIME.
		/// </summary>
		public static DateTime GetDateTime(System.Runtime.InteropServices.ComTypes.FILETIME filetime)
		{
            // check for invalid value.
            if (filetime.dwHighDateTime < 0)
            {
                return DateTime.MinValue;
            }

			// convert FILETIME structure to a 64 bit integer.
			long buffer = (long)filetime.dwHighDateTime;

			if (buffer < 0)
			{
				buffer += ((long)UInt32.MaxValue+1);
			}

			long ticks = (buffer<<32);

			buffer = (long)filetime.dwLowDateTime;

			if (buffer < 0)
			{
				buffer += ((long)UInt32.MaxValue+1);
			}

			ticks += buffer;

			// check for invalid value.
			if (ticks == 0)
			{
				return DateTime.MinValue;
			}

			// adjust for WIN32 FILETIME base.			
			return FILETIME_BaseTime.Add(new TimeSpan(ticks));
		}        

		/// <summary>
		/// Marshals an array of DateTimes into an unmanaged array of FILETIMEs
		/// </summary>
		/// <param name="datetimes">The array of DateTimes to marshal</param>
		/// <returns>The IntPtr array of FILETIMEs</returns>
		public static IntPtr GetFILETIMEs(DateTime[] datetimes)
		{
			int count = (datetimes != null)?datetimes.Length:0;

			if (count <= 0)
			{
				return IntPtr.Zero;
			}

			IntPtr pFiletimes = Marshal.AllocCoTaskMem(count*Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME)));

			IntPtr pos = pFiletimes;

			for (int ii = 0; ii < count; ii++)
			{
				Marshal.StructureToPtr(GetFILETIME(datetimes[ii]), pos, false);
				pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME)));
			}

			return pFiletimes;
		}	

		/// <summary>
		/// Unmarshals an array of WIN32 FILETIMEs as DateTimes.
		/// </summary>
		public static DateTime[] GetDateTimes(ref IntPtr pArray, int size, bool deallocate)
		{
			if (pArray == IntPtr.Zero || size <= 0)
			{
				return null;
			}

			DateTime[] datetimes = new DateTime[size];

			IntPtr pos = pArray;

			for (int ii = 0; ii < size; ii++)
			{
				datetimes[ii] = GetDateTime(pos);
				pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME)));
			}

			if (deallocate)
			{
				Marshal.FreeCoTaskMem(pArray);
				pArray = IntPtr.Zero;
			}

			return datetimes;
		}	

		/// <summary>
		/// Unmarshals an array of WIN32 GUIDs as Guid.
		/// </summary>
		public static Guid[] GetGUIDs(ref IntPtr pInput, int size, bool deallocate)
		{
			if (pInput == IntPtr.Zero || size <= 0)
			{
				return null;
			}

			Guid[] guids = new Guid[size];

			IntPtr pos = pInput;

			for (int ii = 0; ii < size; ii++)
			{
				GUID input = (GUID)Marshal.PtrToStructure(pInput, typeof(GUID));

				guids[ii] = new Guid(input.Data1, input.Data2, input.Data3, input.Data4);
				
				pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(GUID)));
			}

			if (deallocate)
			{
				Marshal.FreeCoTaskMem(pInput);
				pInput = IntPtr.Zero;
			}

			return guids;
		}	
        
		/// <summary>
		/// Converts an object into a value that can be marshalled to a VARIANT.
		/// </summary>
		/// <param name="source">The object to convert.</param>
		/// <returns>The converted object.</returns>
        public static object GetVARIANT(object source)
        {
			// check for invalid args.
			if (source == null)
			{
				return null;
			}

            return GetVARIANT(source, TypeInfo.Construct(source));
        }

        /// <summary>
        /// Converts an object into a value that can be marshalled to a VARIANT.
        /// </summary>
        /// <param name="source">The object to convert.</param>
        /// <returns>The converted object.</returns>
        public static object GetVARIANT(Variant source)
        {
            return GetVARIANT(source.Value, source.TypeInfo);
        }
   
        /// <summary>
        /// Converts an object into a value that can be marshalled to a VARIANT.
        /// </summary>
        /// <param name="source">The object to convert.</param>
        /// <param name="typeInfo">The type info.</param>
        /// <returns>The converted object.</returns>
        public static object GetVARIANT(object source, TypeInfo typeInfo)
		{
			// check for invalid args.
			if (source == null)
			{
				return null;
			}
            
            try
            {
                switch (typeInfo.BuiltInType)
                {
                    case BuiltInType.Boolean:
                    case BuiltInType.Byte:
                    case BuiltInType.Int16:
                    case BuiltInType.UInt16:
                    case BuiltInType.Int32:
                    case BuiltInType.UInt32:
                    case BuiltInType.Int64:
                    case BuiltInType.UInt64:
                    case BuiltInType.Float:
                    case BuiltInType.Double:
                    case BuiltInType.String:
                    case BuiltInType.DateTime:
                    {
                        return source;
                    }

                    case BuiltInType.ByteString:
                    {
                        if (typeInfo.ValueRank < 0)
                        {
                            return source;
                        }
                        
                        return VariantToObjectArray((Array)source);
                    }

                    case BuiltInType.Guid:
                    case BuiltInType.LocalizedText:
                    case BuiltInType.QualifiedName:
                    case BuiltInType.NodeId:
                    case BuiltInType.ExpandedNodeId:                  
                    case BuiltInType.XmlElement:  
                    {
                        return TypeInfo.Cast(source, typeInfo, BuiltInType.String);
                    }

                    case BuiltInType.StatusCode: 
                    {
                        return TypeInfo.Cast(source, typeInfo, BuiltInType.UInt32);
                    }
                       
                    case BuiltInType.Variant:
                    {
                        if (typeInfo.ValueRank < 0)
                        {
                            return GetVARIANT(((Variant)source).Value);
                        }

                        return VariantToObjectArray((Array)source);
                    }

                    case BuiltInType.ExtensionObject:
                    {
                        if (typeInfo.ValueRank < 0)
                        {
                            byte[] body = null;

                            ExtensionObject extension = (ExtensionObject)source;

                            switch (extension.Encoding)
                            {
                                case ExtensionObjectEncoding.Binary: 
                                {
                                    body = (byte[])extension.Body;
                                    break;
                                }

                                case ExtensionObjectEncoding.Xml: 
                                {
                                    body = new UTF8Encoding().GetBytes(((XmlElement)extension.Body).OuterXml);
                                    break;
                                }

                                case ExtensionObjectEncoding.EncodeableObject: 
                                {
                                    BinaryEncoder encoder = new BinaryEncoder(ServiceMessageContext.GlobalContext);
                                    encoder.WriteEncodeable(null, (IEncodeable)extension.Body, null);
                                    body = encoder.CloseAndReturnBuffer();
                                    break;
                                }
                            }

                            return body;
                        }

                        return VariantToObjectArray((Array)source);
                    }
                       
                    case BuiltInType.DataValue:
                    case BuiltInType.DiagnosticInfo:
                    {
                        return "(unsupported)";
                    }
                }
            }
            catch (Exception e)
            {
                return e.Message;
            }

			// no conversion required.
			return source;
		}

        /// <summary>
        /// Converts a Variant array to an Object array.
        /// </summary>
        private static Array VariantToObjectArray(Array input)
        {
            int[] dimensions = new int[input.Rank];

            for (int ii = 0; ii < dimensions.Length; ii++)
            {
                dimensions[ii] = input.GetLength(ii);
            }
            
            Array output = Array.CreateInstance(typeof(object), dimensions);
                                
            int length = output.Length;
            int[] indexes = new int[dimensions.Length];

            for (int ii = 0; ii < length; ii++)
            {
                int divisor = output.Length;

                for (int jj = 0; jj < indexes.Length; jj++)
                {
                    divisor /= dimensions[jj];
                    indexes[jj] = (ii/divisor)%dimensions[jj];
                }
                
                object value = input.GetValue(indexes);
                output.SetValue(GetVARIANT(value), indexes);
            }

            return output;
        }

		/// <summary>
		/// Marshals an array objects into an unmanaged array of VARIANTs.
		/// </summary>
		/// <param name="values">An array of the objects to be marshalled</param>
		/// <param name="preprocess">Whether the objects should have troublesome types removed before marhalling.</param>
		/// <returns>An pointer to the array in unmanaged memory</returns>
		public static IntPtr GetVARIANTs(object[] values, bool preprocess)
		{
			int count = (values != null)?values.Length:0;

			if (count <= 0)
			{
				return IntPtr.Zero;
			}

			IntPtr pValues = Marshal.AllocCoTaskMem(count*VARIANT_SIZE);

			IntPtr pos = pValues;

			for (int ii = 0; ii < count; ii++)
			{
				if (preprocess)
				{
					Marshal.GetNativeVariantForObject(GetVARIANT(values[ii]), pos);
				}
				else
				{
					Marshal.GetNativeVariantForObject(values[ii], pos);
				}

				pos = (IntPtr)(pos.ToInt64() + VARIANT_SIZE);
			}

			return pValues;
		}	

		/// <summary>
		/// Unmarshals an array of VARIANTs as objects.
		/// </summary>
		public static object[] GetVARIANTs(ref IntPtr pArray, int size, bool deallocate)
		{
			// this method unmarshals VARIANTs one at a time because a single bad value throws 
			// an exception with GetObjectsForNativeVariants(). This approach simply sets the 
			// offending value to null.

			if (pArray == IntPtr.Zero || size <= 0)
			{
				return null;
			}

			object[] values = new object[size];

			IntPtr pos = pArray;

			for (int ii = 0; ii < size; ii++)
			{
				try 
				{
					values[ii] = Marshal.GetObjectForNativeVariant(pos);
					if (deallocate) VariantClear(pos);
				}
				catch (Exception)
				{
					values[ii] = null;
				}

				pos = (IntPtr)(pos.ToInt64() + VARIANT_SIZE);
			}

			if (deallocate)
			{
				Marshal.FreeCoTaskMem(pArray);
				pArray = IntPtr.Zero;
			}

			return values;
		}	

		/// <summary>
		/// Converts a LCID to a Locale string.
		/// </summary>
		public static string GetLocale(int input)
		{
			try   
			{ 
				if (input == LOCALE_SYSTEM_DEFAULT || input == LOCALE_USER_DEFAULT || input == 0)
				{
					return CultureInfo.InvariantCulture.Name;
				}

				return new CultureInfo(input).Name; 
			}
			catch (Exception e)
			{ 
			    throw new OpcResultException(StatusCodes.Bad, "Unrecognized locale provided.", e);
			}
		}

		/// <summary>
		/// Converts a Locale string to a LCID.
		/// </summary>
		public static int GetLocale(string input)
		{
			// check for the default culture.
			if (input == null || input == "")
			{
				return 0;
			}

			CultureInfo locale = null;

			try   { locale = new CultureInfo(input);     }
			catch { locale = CultureInfo.CurrentCulture; }
		
			return locale.LCID;
		}

		/// <summary>
		/// Converts the VARTYPE to a UA Data Type ID.
		/// </summary>
		public static NodeId GetDataTypeId(short input)
		{				
			switch ((VarEnum)Enum.ToObject(typeof(VarEnum), (input & ~(short)VarEnum.VT_ARRAY)))
			{
				case VarEnum.VT_I1:      return DataTypes.SByte;
				case VarEnum.VT_UI1:     return DataTypes.Byte;
				case VarEnum.VT_I2:      return DataTypes.Int16;
				case VarEnum.VT_UI2:     return DataTypes.UInt16;
				case VarEnum.VT_I4:      return DataTypes.Int32;
				case VarEnum.VT_UI4:     return DataTypes.UInt32;
				case VarEnum.VT_I8:      return DataTypes.Int64;
				case VarEnum.VT_UI8:     return DataTypes.UInt64;
				case VarEnum.VT_R4:      return DataTypes.Float;
				case VarEnum.VT_R8:      return DataTypes.Double;
				case VarEnum.VT_BOOL:    return DataTypes.Boolean;
				case VarEnum.VT_DATE:    return DataTypes.DateTime;
				case VarEnum.VT_BSTR:    return DataTypes.String;
				case VarEnum.VT_CY:      return DataTypes.String;
				case VarEnum.VT_EMPTY:   return DataTypes.BaseDataType;
				
                case VarEnum.VT_VARIANT: 
                { 
                    return DataTypes.BaseDataType;
                }
			}

			return NodeId.Null;
		}
        
		/// <summary>
		/// Converts the VARTYPE to a UA ValueRank.
		/// </summary>
		public static int GetValueRank(short input)
		{				
            if ((((short)VarEnum.VT_ARRAY & input) != 0))
            {
                return ValueRanks.OneDimension;
            }

            return ValueRanks.Scalar;
		}

		/// <summary>
		/// Converts the VARTYPE to a UA Data Type ID.
		/// </summary>
		public static NodeId GetDataTypeId(short input, out bool isArray)
		{				
			isArray = (((short)VarEnum.VT_ARRAY & input) != 0);
			return GetDataTypeId(input);
		}
        
		/// <summary>
		/// Converts the VARTYPE to a SystemType
		/// </summary>
		public static Type GetSystemType(short input)
		{				
			bool isArray = (((short)VarEnum.VT_ARRAY & input) != 0);
            VarEnum varType = (VarEnum)Enum.ToObject(typeof(VarEnum), (input & ~(short)VarEnum.VT_ARRAY));

            if (!isArray)
            {
			    switch (varType)
			    {
				    case VarEnum.VT_I1:      return typeof(sbyte);
				    case VarEnum.VT_UI1:     return typeof(byte);
				    case VarEnum.VT_I2:      return typeof(short);
				    case VarEnum.VT_UI2:     return typeof(ushort);
				    case VarEnum.VT_I4:      return typeof(int);
				    case VarEnum.VT_UI4:     return typeof(uint);
				    case VarEnum.VT_I8:      return typeof(long);
				    case VarEnum.VT_UI8:     return typeof(ulong);
				    case VarEnum.VT_R4:      return typeof(float);
				    case VarEnum.VT_R8:      return typeof(double);
				    case VarEnum.VT_BOOL:    return typeof(bool);
				    case VarEnum.VT_DATE:    return typeof(DateTime);
				    case VarEnum.VT_BSTR:    return typeof(string);
				    case VarEnum.VT_CY:      return typeof(decimal);
				    case VarEnum.VT_EMPTY:   return typeof(object);
				    case VarEnum.VT_VARIANT: return typeof(object);
			    }
            }
            else
            {
			    switch (varType)
			    {
				    case VarEnum.VT_I1:      return typeof(sbyte[]);
				    case VarEnum.VT_UI1:     return typeof(byte[]);
				    case VarEnum.VT_I2:      return typeof(short[]);
				    case VarEnum.VT_UI2:     return typeof(ushort[]);
				    case VarEnum.VT_I4:      return typeof(int[]);
				    case VarEnum.VT_UI4:     return typeof(uint[]);
				    case VarEnum.VT_I8:      return typeof(long[]);
				    case VarEnum.VT_UI8:     return typeof(ulong[]);
				    case VarEnum.VT_R4:      return typeof(float[]);
				    case VarEnum.VT_R8:      return typeof(double[]);
				    case VarEnum.VT_BOOL:    return typeof(bool[]);
				    case VarEnum.VT_DATE:    return typeof(DateTime[]);
				    case VarEnum.VT_BSTR:    return typeof(string[]);
				    case VarEnum.VT_CY:      return typeof(decimal[]);
				    case VarEnum.VT_EMPTY:   return typeof(object[]);
				    case VarEnum.VT_VARIANT: return typeof(object[]);
			    }
            }

			return null;
		}
        
		/// <summary>
		/// Returns the VARTYPE for the value.
		/// </summary>
		public static VarEnum GetVarType(object input)
		{
            if (input == null)
            {
                return VarEnum.VT_EMPTY;
            }

            return GetVarType(input.GetType());
        }

		/// <summary>
		/// Converts the system type to a VARTYPE.
		/// </summary>
		public static VarEnum GetVarType(System.Type type)
		{
            if (type == null)
            {
                return VarEnum.VT_EMPTY;
            }

            if (type == null)               return VarEnum.VT_EMPTY;
			if (type == typeof(sbyte))      return VarEnum.VT_I1;
			if (type == typeof(byte))       return VarEnum.VT_UI1;
			if (type == typeof(short))      return VarEnum.VT_I2;
			if (type == typeof(ushort))     return VarEnum.VT_UI2;
			if (type == typeof(int))        return VarEnum.VT_I4;
			if (type == typeof(uint))       return VarEnum.VT_UI4;
			if (type == typeof(long))       return VarEnum.VT_I8;
			if (type == typeof(ulong))      return VarEnum.VT_UI8;
			if (type == typeof(float))      return VarEnum.VT_R4;
			if (type == typeof(double))     return VarEnum.VT_R8;
			if (type == typeof(decimal))    return VarEnum.VT_CY;
			if (type == typeof(bool))       return VarEnum.VT_BOOL;
			if (type == typeof(DateTime))   return VarEnum.VT_DATE;
			if (type == typeof(string))     return VarEnum.VT_BSTR;
			if (type == typeof(sbyte[]))    return VarEnum.VT_ARRAY | VarEnum.VT_I1;
			if (type == typeof(byte[]))     return VarEnum.VT_ARRAY | VarEnum.VT_UI1;
			if (type == typeof(short[]))    return VarEnum.VT_ARRAY | VarEnum.VT_I2;
			if (type == typeof(ushort[]))   return VarEnum.VT_ARRAY | VarEnum.VT_UI2;
			if (type == typeof(int[]))      return VarEnum.VT_ARRAY | VarEnum.VT_I4;
			if (type == typeof(uint[]))     return VarEnum.VT_ARRAY | VarEnum.VT_UI4;
			if (type == typeof(long[]))     return VarEnum.VT_ARRAY | VarEnum.VT_I8;
			if (type == typeof(ulong[]))    return VarEnum.VT_ARRAY | VarEnum.VT_UI8;
			if (type == typeof(float[]))    return VarEnum.VT_ARRAY | VarEnum.VT_R4;
			if (type == typeof(double[]))   return VarEnum.VT_ARRAY | VarEnum.VT_R8;
			if (type == typeof(decimal[]))  return VarEnum.VT_ARRAY | VarEnum.VT_CY;
			if (type == typeof(bool[]))     return VarEnum.VT_ARRAY | VarEnum.VT_BOOL;
			if (type == typeof(DateTime[])) return VarEnum.VT_ARRAY | VarEnum.VT_DATE;
			if (type == typeof(string[]))   return VarEnum.VT_ARRAY | VarEnum.VT_BSTR;
			if (type == typeof(object[]))   return VarEnum.VT_ARRAY | VarEnum.VT_VARIANT;
			
			return VarEnum.VT_EMPTY;
		}

        /// <summary>
        /// Converts the TypeInfo to a VARTYPE.
        /// </summary>
        public static VarEnum GetVarType(TypeInfo typeInfo)
        {
            if (typeInfo == null)
            {
                return VarEnum.VT_EMPTY;
            }

            VarEnum vtType = VarEnum.VT_EMPTY;

            switch (typeInfo.BuiltInType)
            {
                case BuiltInType.Boolean: { vtType = VarEnum.VT_BOOL; break; }
                case BuiltInType.SByte: { vtType = VarEnum.VT_I1; break; } 
                case BuiltInType.Byte: { vtType = VarEnum.VT_UI1; break; }
                case BuiltInType.Int16: { vtType = VarEnum.VT_I2; break; }
                case BuiltInType.UInt16: { vtType = VarEnum.VT_UI2; break; }
                case BuiltInType.Int32: { vtType = VarEnum.VT_I4; break; }
                case BuiltInType.UInt32: { vtType = VarEnum.VT_UI4; break; }
                case BuiltInType.Int64: { vtType = VarEnum.VT_I8; break; }
                case BuiltInType.UInt64: { vtType = VarEnum.VT_UI8; break; }
                case BuiltInType.Float: { vtType = VarEnum.VT_R4; break; }
                case BuiltInType.Double: { vtType = VarEnum.VT_R8; break; }
                case BuiltInType.String: { vtType = VarEnum.VT_BSTR; break; }
                case BuiltInType.DateTime: { vtType = VarEnum.VT_DATE; break; }
                case BuiltInType.Guid: { vtType = VarEnum.VT_BSTR; break; }
                case BuiltInType.ByteString: { vtType = VarEnum.VT_ARRAY | VarEnum.VT_UI1; break; }
                case BuiltInType.XmlElement: { vtType = VarEnum.VT_BSTR; break; }
                case BuiltInType.NodeId: { vtType = VarEnum.VT_BSTR; break; }
                case BuiltInType.ExpandedNodeId: { vtType = VarEnum.VT_BSTR; break; }
                case BuiltInType.QualifiedName: { vtType = VarEnum.VT_BSTR; break; }
                case BuiltInType.LocalizedText: { vtType = VarEnum.VT_BSTR; break; }
                case BuiltInType.StatusCode: { vtType = VarEnum.VT_UI4; break; }
                case BuiltInType.ExtensionObject: { vtType = VarEnum.VT_ARRAY | VarEnum.VT_UI1; break; }
                case BuiltInType.Enumeration: { vtType = VarEnum.VT_I4; break; }
                case BuiltInType.Number: { vtType = VarEnum.VT_R8; break; }
                case BuiltInType.Integer: { vtType = VarEnum.VT_I8; break; }
                case BuiltInType.UInteger: { vtType = VarEnum.VT_UI8; break; }
                
                case BuiltInType.Variant: 
                {
                    if (typeInfo.ValueRank == ValueRanks.Scalar)
                    {
                        return VarEnum.VT_EMPTY;
                    }

                    vtType = VarEnum.VT_VARIANT;
                    break;
                }

                default:
                {
                    return VarEnum.VT_EMPTY;
                }
            }

            if (typeInfo.ValueRank > 0)
            {
                vtType |= VarEnum.VT_ARRAY;
            }

            return vtType;
        }
        
		/// <summary>
		/// Converts a value to the specified type using COM conversion rules.
		/// </summary>
        public static int ChangeTypeForCOM(object source, VarEnum targetType, out object target)
        {
            return ChangeTypeForCOM(source, GetVarType(source), targetType, out target);
        }

		/// <summary>
		/// Converts a value to the specified type using COM conversion rules.
		/// </summary>
		public static int ChangeTypeForCOM(object source, VarEnum sourceType, VarEnum targetType, out object target)
		{
            target = source;
            
            // check for trivial case.
            if (sourceType == targetType)
            {
                return ResultIds.S_OK;
            }

			// check for conversions to date time from string.
            string stringValue = source as string;

			if (stringValue != null && targetType == VarEnum.VT_DATE)
			{
				try   
                { 
                    target = System.Convert.ToDateTime(stringValue);
                    return ResultIds.S_OK;
                }
				catch 
                {
                    target = null;
                    return ResultIds.DISP_E_OVERFLOW;
                }
			}

			// check for conversions from date time to boolean.
            if (sourceType == VarEnum.VT_DATE && targetType == VarEnum.VT_BOOL)
			{
				target = !(new DateTime(1899, 12, 30, 0, 0, 0).Equals((DateTime)source));
                return ResultIds.S_OK;
			}

			// check for conversions from float to double.
            if (sourceType == VarEnum.VT_R4 && targetType == VarEnum.VT_R8)
			{
				target = System.Convert.ToDouble((float)source);
                return ResultIds.S_OK;
			}

			// check for array conversion.
            Array array = source as Array;
            bool targetIsArray = ((targetType & VarEnum.VT_ARRAY) != 0);

			if (array != null && targetIsArray)
			{
                VarEnum elementType = (VarEnum)((short)targetType & ~(short)VarEnum.VT_ARRAY);

                Array convertedArray = Array.CreateInstance(GetSystemType((short)elementType), array.Length);

                for (int ii = 0; ii < array.Length; ii++)
                {
                    object elementValue = null;
                    int error = ChangeTypeForCOM(array.GetValue(ii), elementType, out elementValue);

                    if (error < 0)
                    {
                        target = null;
                        return ResultIds.DISP_E_OVERFLOW;
                    }
                        
                    convertedArray.SetValue(elementValue, ii);
                }

				target = convertedArray;
                return ResultIds.S_OK;
			}
			else if (array == null && !targetIsArray)
			{
				IntPtr pvargDest = Marshal.AllocCoTaskMem(16);
				IntPtr pvarSrc   = Marshal.AllocCoTaskMem(16);
				
				VariantInit(pvargDest);
				VariantInit(pvarSrc);

				Marshal.GetNativeVariantForObject(source, pvarSrc);

				try
				{
					// change type.
					int error = VariantChangeTypeEx(
						pvargDest, 
						pvarSrc, 
						Thread.CurrentThread.CurrentCulture.LCID, 
						VARIANT_NOVALUEPROP | VARIANT_ALPHABOOL, 
						(short)targetType);

					// check error code.
					if (error != 0)
					{
                        target = null;
                        return error;
					}

					// unmarshal result.
					object result = Marshal.GetObjectForNativeVariant(pvargDest);
					
					// check for invalid unsigned <=> signed conversions.
					switch (targetType)
					{
						case VarEnum.VT_I1:
						case VarEnum.VT_I2:
						case VarEnum.VT_I4:
						case VarEnum.VT_I8:
						case VarEnum.VT_UI1:
						case VarEnum.VT_UI2:
						case VarEnum.VT_UI4:
						case VarEnum.VT_UI8:
						{
							// ignore issue for conversions from boolean.
                            if (sourceType == VarEnum.VT_BOOL)
							{	
								break;					
							}

							decimal sourceAsDecimal = 0;
							decimal resultAsDecimal = System.Convert.ToDecimal(result);

							try   { sourceAsDecimal = System.Convert.ToDecimal(source); }
							catch { sourceAsDecimal = 0; }
							
							if ((sourceAsDecimal < 0 && resultAsDecimal > 0) || (sourceAsDecimal > 0 && resultAsDecimal < 0))
							{
                                target = null;
                                return ResultIds.E_RANGE;
							}
                            
							// conversion from datetime should have failed.
                            if (sourceType == VarEnum.VT_DATE)
							{	
                                if (resultAsDecimal == 0)
                                {
                                    target = null;
                                    return ResultIds.E_RANGE;
                                }
							}

							break;
						}		
							
						case VarEnum.VT_R8:
						{						
							// fix precision problem introduced with conversion from float to double.
                            if (sourceType == VarEnum.VT_R4)
							{	
								result = System.Convert.ToDouble(source.ToString());
							}

							break;			
						}
					}

					target = result;
                    return ResultIds.S_OK;
				}
				finally
				{
					VariantClear(pvargDest);
					VariantClear(pvarSrc);

					Marshal.FreeCoTaskMem(pvargDest);
					Marshal.FreeCoTaskMem(pvarSrc);
				}
			}
			else if (array != null && targetType == VarEnum.VT_BSTR)
			{
				int count = ((Array)source).Length;

				StringBuilder buffer = new StringBuilder();

				buffer.Append("{");

				foreach (object element in (Array)source)
				{
                    object elementValue = null;
               
                    int error = ChangeTypeForCOM(element, VarEnum.VT_BSTR, out elementValue);

                    if (error < 0)
                    {
                        target = null;
                        return error;
                    }
                    
					buffer.Append((string)elementValue);

					count--;

					if (count > 0)
					{
						buffer.Append(" | ");
					}
				}

				buffer.Append("}");

				target = buffer.ToString();
                return ResultIds.S_OK;
			}
            
			// no conversions between scalar and array types allowed.
            target = null;
            return ResultIds.E_BADTYPE;
		}
        
		/// <summary>
		/// Returns true is the object is a valid COM type.
		/// </summary>
		public static bool IsValidComType(object input)
		{				
            Array array = input as Array;

            if (array != null)
            {
                foreach (object value in array)
                {
                    if (!IsValidComType(value))
                    {
                        return false;
                    }
                }

                return true;
            }
            
            return GetVarType(input) != VarEnum.VT_EMPTY;
		}

        /// <summary>
        /// Converts a COM value to something that UA clients can deal with.
        /// </summary>
        public static object ProcessComValue(object value)
        {     
            // flatten any multi-dimensional array.
            Array array = value as Array;

            if (array != null)
            {
                if (array.Rank > 1)
                {
                    value = array = Utils.FlattenArray(array);
                }
                
                // convert array of decimal to strings.
                if (array != null && array.GetType().GetElementType() == typeof(decimal))
                {
                    string[] clone = new string[array.Length];

                    for (int ii = 0; ii < array.Length; ii++)
                    {
                        clone[ii] = Convert.ToString(array.GetValue(ii));
                    }
                    
                    value = clone;                        
                }
            }

            // convert scalar decimal to a string.
            if (value is decimal)
            {
                value = Convert.ToString(value);
            }

            return value;
        }

		/// <summary>
		/// Converts a DA quality code to a status code.
		/// </summary>
		public static StatusCode GetQualityCode(short quality)
		{
			StatusCode code = 0;

			// convert quality status.
			switch ((short)(quality & 0x00FC))
			{			
                case OpcRcw.Da.Qualities.OPC_QUALITY_GOOD:                     { code = StatusCodes.Good;                              break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_LOCAL_OVERRIDE:           { code = StatusCodes.GoodLocalOverride;                 break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_UNCERTAIN:                { code = StatusCodes.Uncertain;                         break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_SUB_NORMAL:               { code = StatusCodes.UncertainSubNormal;                break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_CAL:               { code = StatusCodes.UncertainSensorNotAccurate;        break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_EGU_EXCEEDED:             { code = StatusCodes.UncertainEngineeringUnitsExceeded; break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_LAST_USABLE:              { code = StatusCodes.UncertainLastUsableValue;          break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_BAD:                      { code = StatusCodes.Bad;                               break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_CONFIG_ERROR:             { code = StatusCodes.BadConfigurationError;             break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_NOT_CONNECTED:            { code = StatusCodes.BadNotConnected;                   break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_COMM_FAILURE:             { code = StatusCodes.BadNoCommunication;                break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_DEVICE_FAILURE:           { code = StatusCodes.BadDeviceFailure;                  break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_FAILURE:           { code = StatusCodes.BadSensorFailure;                  break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_LAST_KNOWN:               { code = StatusCodes.BadOutOfService;                   break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_OUT_OF_SERVICE:           { code = StatusCodes.BadOutOfService;                   break; }
                case OpcRcw.Da.Qualities.OPC_QUALITY_WAITING_FOR_INITIAL_DATA: { code = StatusCodes.BadWaitingForInitialData;          break; }
			}
            
			// convert the limit status.
			switch ((short)(quality & 0x0003))
			{		
				case OpcRcw.Da.Qualities.OPC_LIMIT_LOW:   { code.LimitBits = LimitBits.Low;      break; }
				case OpcRcw.Da.Qualities.OPC_LIMIT_HIGH:  { code.LimitBits = LimitBits.High;     break; }
				case OpcRcw.Da.Qualities.OPC_LIMIT_CONST: { code.LimitBits = LimitBits.Constant; break; }
			}

			// return the combined code.
			return code;
		}

		/// <summary>
		/// Converts a UA status code to a DA quality code.
		/// </summary>
		public static short GetQualityCode(StatusCode input)
		{
			short code = 0;

			// convert quality status.
			switch (input.CodeBits)
			{			
				case StatusCodes.Good:                              { code = OpcRcw.Da.Qualities.OPC_QUALITY_GOOD;           break; }
				case StatusCodes.GoodLocalOverride:                 { code = OpcRcw.Da.Qualities.OPC_QUALITY_LOCAL_OVERRIDE; break; }
				case StatusCodes.Uncertain:                         { code = OpcRcw.Da.Qualities.OPC_QUALITY_UNCERTAIN;      break; }
				case StatusCodes.UncertainSubNormal:                { code = OpcRcw.Da.Qualities.OPC_QUALITY_SUB_NORMAL;     break; }
				case StatusCodes.UncertainSensorNotAccurate:        { code = OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_CAL;     break; }
				case StatusCodes.UncertainEngineeringUnitsExceeded: { code = OpcRcw.Da.Qualities.OPC_QUALITY_EGU_EXCEEDED;   break; }
				case StatusCodes.UncertainLastUsableValue:          { code = OpcRcw.Da.Qualities.OPC_QUALITY_LAST_USABLE;    break; }
				case StatusCodes.Bad:                               { code = OpcRcw.Da.Qualities.OPC_QUALITY_BAD;            break; }
				case StatusCodes.BadConfigurationError:             { code = OpcRcw.Da.Qualities.OPC_QUALITY_CONFIG_ERROR;   break; }
				case StatusCodes.BadNotConnected:                   { code = OpcRcw.Da.Qualities.OPC_QUALITY_NOT_CONNECTED;  break; }
                case StatusCodes.BadNoCommunication:                { code = OpcRcw.Da.Qualities.OPC_QUALITY_COMM_FAILURE;   break; }
				case StatusCodes.BadOutOfService:                   { code = OpcRcw.Da.Qualities.OPC_QUALITY_OUT_OF_SERVICE; break; }
				case StatusCodes.BadDeviceFailure:                  { code = OpcRcw.Da.Qualities.OPC_QUALITY_DEVICE_FAILURE; break; }
				case StatusCodes.BadSensorFailure:                  { code = OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_FAILURE; break; }
				case StatusCodes.BadWaitingForInitialData:          { code = OpcRcw.Da.Qualities.OPC_QUALITY_WAITING_FOR_INITIAL_DATA; break; }

                default:
                {
                    if (StatusCode.IsBad(input))
                    {
                        code = OpcRcw.Da.Qualities.OPC_QUALITY_BAD;
                        break;
                    }
                    
                    if (StatusCode.IsUncertain(input))
                    {
                        code = OpcRcw.Da.Qualities.OPC_QUALITY_UNCERTAIN;
                        break;
                    }

                    code = OpcRcw.Da.Qualities.OPC_QUALITY_GOOD;
                    break;
                }
			}

			// convert the limit status.
			switch (input.LimitBits)
			{		
                case LimitBits.Low:      { code |= OpcRcw.Da.Qualities.OPC_LIMIT_LOW;   break; }
                case LimitBits.High:     { code |= OpcRcw.Da.Qualities.OPC_LIMIT_HIGH;  break; }
                case LimitBits.Constant: { code |= OpcRcw.Da.Qualities.OPC_LIMIT_CONST; break; }
			}

			// return the combined code.
			return code;
		}

        /// <summary>
        /// Converts a HDA quality code to a StatusCode.
        /// </summary>
        public static StatusCode GetHdaQualityCode(uint quality)
        {
            uint hdaCode = quality & 0xFFFF0000;
            
            // check for bits indicating an out right error.
            if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_NOBOUND) != 0)
            {
                return StatusCodes.BadBoundNotFound;
            }

            if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_NODATA) != 0)
            {
                return StatusCodes.BadNoData;
            }

            if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_DATALOST) != 0)
            {
                return StatusCodes.BadDataLost;
            }

            if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_CONVERSION) != 0)
            {
                return StatusCodes.BadTypeMismatch;
            }

            // Get DA part (lower 2 bytes).
            StatusCode code = GetQualityCode((short)(quality & 0x0000FFFF));
            
            // check for bits that are placed in the info bits.
            AggregateBits aggregateBits = 0;
            
            if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_EXTRADATA) != 0)
            {
                aggregateBits |= AggregateBits.ExtraData;
            }

            if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_INTERPOLATED) != 0)
            {
                aggregateBits |= AggregateBits.Interpolated;
            }

            if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_RAW) != 0)
            {
                aggregateBits |= AggregateBits.Raw;
            }

            if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_CALCULATED) != 0)
            {
                aggregateBits |= AggregateBits.Calculated;
            }

            if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_PARTIAL) != 0)
            {
                aggregateBits |= AggregateBits.Partial;
            }

            return code.SetAggregateBits(aggregateBits);
        }

        /// <summary>
        /// Converts a UA status code to a HDA quality code.
        /// </summary>
        public static uint GetHdaQualityCode(StatusCode input)
        {
            // check for fatal errors.
            switch (input.CodeBits)
            {
                case StatusCodes.BadBoundNotFound: { return OpcRcw.Hda.Constants.OPCHDA_NOBOUND; }
                case StatusCodes.BadBoundNotSupported: { return OpcRcw.Hda.Constants.OPCHDA_NOBOUND; }
                case StatusCodes.BadNoData: { return OpcRcw.Hda.Constants.OPCHDA_NODATA; }
                case StatusCodes.BadDataLost: { return OpcRcw.Hda.Constants.OPCHDA_DATALOST; }
            }

            // handle normal case.
            uint code = Utils.ToUInt32(GetQualityCode(input));
            
            // check for bits that are placed in the info bits.
            AggregateBits aggregateBits = input.AggregateBits;

            if ((aggregateBits & AggregateBits.ExtraData) != 0)
            {
                code |= OpcRcw.Hda.Constants.OPCHDA_EXTRADATA;
            }

            // set the source of the data.
            if ((aggregateBits & AggregateBits.Interpolated) != 0)
            {
                code |= OpcRcw.Hda.Constants.OPCHDA_INTERPOLATED;
            }
            else if ((aggregateBits & AggregateBits.Calculated) != 0)
            {
                code |= OpcRcw.Hda.Constants.OPCHDA_CALCULATED;
            }
            else if ((aggregateBits & AggregateBits.Partial) != 0)
            {
                code |= OpcRcw.Hda.Constants.OPCHDA_PARTIAL;
            }
            else
            {
                code |= OpcRcw.Hda.Constants.OPCHDA_RAW;
            }

            // return the combined code.
            return code;
        }
#endif

        /// <summary>
        /// Returns the symbolic name for the specified error.
        /// </summary>
        public static string GetErrorText(Type type, int error)
        {
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static);

            foreach (var field in fields)
            {
                if (error == (int)field.GetValue(type))
                {
                    return field.Name;
                }
            }

            return string.Format("0x{0:X8}", error);
        }

        /// <summary>
        /// Gets the error code for the exception.
        /// </summary>
        /// <param name="e">The exception.</param>
        /// <param name="defaultCode">The default code.</param>
        /// <returns>The error code</returns>
        /// <remarks>This method ignores the exception but makes it possible to keep track of ignored exceptions.</remarks>
        public static int GetErrorCode(Exception e, int defaultCode)
        {
            return defaultCode;
        }

        /// <summary>
		/// Releases the server if it is a true COM server.
		/// </summary>
		public static void ReleaseServer(object server)
        {
            if (server != null && server.GetType().IsCOMObject)
            {
                Marshal.ReleaseComObject(server);
            }
        }

        /// <summary>
        /// Retrieves the system message text for the specified error.
        /// </summary>
        public static string GetSystemMessage(int error, int localeId)
        {
            int langId;
            switch (localeId)
            {
                case LOCALE_SYSTEM_DEFAULT:
                    {
                        langId = GetSystemDefaultLangID();
                        break;
                    }

                case LOCALE_USER_DEFAULT:
                    {
                        langId = GetUserDefaultLangID();
                        break;
                    }

                default:
                    {
                        langId = (0xFFFF & localeId);
                        break;
                    }
            }

            var buffer = Marshal.AllocCoTaskMem(MAX_MESSAGE_LENGTH);

            var result = FormatMessageW(
                (int)FORMAT_MESSAGE_FROM_SYSTEM,
                IntPtr.Zero,
                error,
                langId,
                buffer,
                MAX_MESSAGE_LENGTH - 1,
                IntPtr.Zero);

            if (result > 0)
            {
                var msg = Marshal.PtrToStringUni(buffer);
                Marshal.FreeCoTaskMem(buffer);

                if (msg != null && msg.Length > 0)
                {
                    return msg.Trim();
                }
            }

            return string.Format("0x{0:X8}", error);
        }

        /// <summary>
        /// Converts an exception to an exception that returns a COM error code.
        /// </summary>
        public static Exception CreateComException(Exception e, int errorId)
        {
            return new COMException(e.Message, errorId);
        }

        /// <summary>
        /// Creates a COM exception.
        /// </summary>
        public static Exception CreateComException(string message, int errorId)
        {
            return new COMException(message, errorId);
        }

        /// <summary>
        /// Converts an exception to an exception that returns a COM error code.
        /// </summary>
        public static Exception CreateComException(int errorId)
        {
            return new COMException(string.Format("0x{0:X8}", errorId), errorId);
        }

        /// <summary>
        /// Converts an exception to an exception that returns a COM error code.
        /// </summary>
        public static Exception CreateComException(Exception e)
        {
            // nothing special required for external exceptions.
            if (e is COMException)
            {
                return e;
            }

            // convert other exceptions to E_FAIL.
            return new COMException(e.Message, OpcResult.E_FAIL.Code);
        }

        /// <summary>
        /// Creates an error message for a failed COM function call.
        /// </summary>
        public static Exception CreateException(Exception e, string function)
        {
            return new OpcResultException(new OpcResult((int)OpcResult.E_NETWORK_ERROR.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Call to {0} failed. Error: {1}.", function, GetSystemMessage(Marshal.GetHRForException(e), LOCALE_SYSTEM_DEFAULT)));
        }

        /// <summary>
        /// Checks if the error is an RPC error.
        /// </summary>
        public static bool IsRpcError(Exception e)
        {
            var error = Marshal.GetHRForException(e);

            // Assume that any 0x8007 is a fatal communication error.
            // May need to update this check if the assumption proves to be incorrect.
            if ((error & 0xFFFF0000) == 0x80070000)
            {
                error &= 0xFFFF;

                //  check the RPC error range define in WinError.h
                if (error >= 1700 && error < 1918)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Checks if the error for the exception is one of the recognized errors.
        /// </summary>
        public static bool IsUnknownError(Exception e, params int[] knownErrors)
        {
            var error = Marshal.GetHRForException(e);

            if (knownErrors != null)
            {
                for (var ii = 0; ii < knownErrors.Length; ii++)
                {
                    if (knownErrors[ii] == error)
                    {
                        return false;
                    }
                }
            }

            return true;
        }
        #endregion

        #region Utility Functions
        /// <summary>
        /// Compares a string locale to a WIN32 localeId
        /// </summary>
        public static bool CompareLocales(int localeId, string locale, bool ignoreRegion)
        {
            // parse locale.
            CultureInfo culture;
            try
            {
                culture = new CultureInfo(locale);
            }
            catch (Exception)
            {
                return false;
            }

            // only match the language portion of the locale id.
            if (ignoreRegion)
            {
                if ((localeId & culture.LCID & 0x3FF) == (localeId & 0x3FF))
                {
                    return true;
                }
            }

            // check for exact match.
            else
            {
                if (localeId == culture.LCID)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Reports an unexpected exception during a COM operation. 
        /// </summary>
        public static void TraceComError(Exception e, string format, params object[] args)
        {
            var message = Utils.Format(format, args);

            var code = Marshal.GetHRForException(e);

            var error = code.ToString();

            if (error == null)
            {
                return;
            }
        }
        #endregion


    }
}
